<?php
namespace Phad;
class Item {
/**
* The name of your item on disk like `Blog/Page` or `Blog/List`
*/
public string $name;
/** a class const of Phad\Blocks that instructs the compiled item what to return */
public $mode;
/**
* The base path for an item, like `/path/to/dir/Blog/Page`
*/
public string $path;
/**
* Args to pass to your compiled template. Should contain `['phad'=>$phad_instance]`
*/
public array $args;
/**
* Path to the template file like `/path/to/dir/Blog/Page.php`
*/
public string $templateFile;
/**
* The base dir for the phad instance that contains all other relative items
*/
public string $dir;
/** This item's template's source code */
public string $source;
/**
* true to always compile views (but one instance still only compiles once)
*/
public $force_compile = false;
/**
* Stored output so you can call `html()` multiple times without re-executing
*/
protected $html = false;
/**
* Stored output so you can call `html_with_no_data()` multiple times without re-executing
*/
protected $html_with_no_data = false;
/**
* @deprecated and will be removed soon
*/
protected array $routes = [];
/**
* Stored output of your item info, so `info()` can be called multiple times without re-executing
*/
protected $item_info = null;
/** to prevent the same view object from compiling multiple times */
protected $instanceHasBeenCompiled = false;
/** args to pass to every item. If item-specific args are set with the same key, they will override the global args. */
static public array $global_args = [];
/**
* An interface for your item templates
*
* @param $name the name of the item like `Taeluf/Blog` (the item's template should be in `$dir` at `Taeluf/Blog.php`)
* @param $dir the directory items are in
* @param $args arguments to pass to your item's template. Will be `extract`ed prior to `require`.
*/
public function __construct(string $name,string $dir,array $args=[]){
$this->name = $name;
$this->dir = $dir;
$this->args = $args;
$this->args = array_merge(static::$global_args, $args);
$this->path = $this->dir.'/'.$name;
$this->templateFile = $this->dir.'/'.$name.'.php';
}
/**
* Get array of the item's data (instead of html)
* @return array of ONLY the first item's data
*/
public function rows(): array{
$this->compileAsNeeded();
// if ($this->rows !== false)return $this->rows;
$phad_block = \Phad\Blocks::ITEM_DATA;
$args = $this->args;
extract($args);
ob_start();
$ret = require($this->get_compiled_file_path());
$output = ob_get_clean();
// $this->mode = null;
// $this->html = $output;
return $ret;
}
/**
* Get array of sitemap data, used for compiling sitemaps
*/
public function sitemap_data():array{
$this->compileAsNeeded();
$phad_block = \Phad\Blocks::SITEMAP_META;
$args = $this->args;
// extract($args);
ob_start();
$sitemapData = require($this->get_compiled_file_path());
ob_get_clean();
return $sitemapData;
}
/**
* Get array of routes
*/
public function routes():array{
$this->compileAsNeeded();
$phad_block = \Phad\Blocks::ROUTE_META;
$args = $this->args;
// $phad = $args['phad'];
// extract($args);
ob_start();
$routes = require($this->get_compiled_file_path());
ob_get_clean();
return $routes;
}
/**
* Get item info.
* @note If there are multiple items in one template, only returns info for the first item.
*
* @return array of item information
*/
public function info(){
$this->compileAsNeeded();
if ($this->item_info !== null)return $this->item_info;
$phad_block = \Phad\Blocks::ITEM_META;
$args = $this->args;
extract($args);
ob_start();
$itemData = require($this->get_compiled_file_path());
ob_get_clean();
if ($itemData==null)$itemData = (object)['type'=>'script'];
$this->item_info = $itemData;
return $itemData;
}
/**
* Delete the item
*/
public function delete(){
$this->compileAsNeeded();
// if ($this->output !== false)return $this->output;
$phad_block = \Phad\Blocks::FORM_DELETE;
$args = $this->args;
// $args['phad_submit_values'] = $submitArgs;
extract($args);
ob_start();
require($this->get_compiled_file_path());
$output = ob_get_clean();
// $this->output = $output;
return $output;
}
/**
* Submit data for the item
*/
public function submit(){
$this->compileAsNeeded();
// if ($this->output !== false)return $this->output;
$phad_block = \Phad\Blocks::FORM_SUBMIT;
$args = $this->args;
extract($args);
ob_start();
require($this->get_compiled_file_path());
$output = ob_get_clean();
// $this->output = $output;
return $output;
}
/**
* get a 2-dim array of js & css files associated with this view. that is same-named css & js files & css & js files in a same-named subdirectory.
*
* When is say 'same-named' i mean without the `.php` file extension
*
* @return `['js'=>[], 'css'=>[]]` where both the sub-arrays are numeric with file paths as values
*/
public function resource_files(){
$path = $this->path;
$css_path = $path.'.css';
$js_path = $path.'.js';
$files = ['js'=>[], 'css'=>[]];
if (file_exists($js_path)){
$files['js'][] = $js_path;
}
if (file_exists($css_path)){
$files['css'][] = $css_path;
}
if (is_dir($path)){
foreach (scandir($path) as $file){
if (!is_file($path.'/'.$file))continue;
if (substr($file,-4)=='.css')$files['css'][] = $path.'/'.$file;
else if (substr($file,-3)=='.js')$files['js'][] = $path.'/'.$file;
}
}
return $files;
}
/**
* Get the item's html view
*
* @return html for your item
*/
public function html(){
$this->compileAsNeeded();
if ($this->html !== false)return $this->html;
$phad_block = \Phad\Blocks::VIEW;
$args = $this->args;
extract($args);
ob_start();
require($this->get_compiled_file_path());
$output = ob_get_clean();
$this->mode = null;
$this->html = $output;
return $output;
}
/**
* Get an html view with no data items
*
* @return string html (or whatever your item's view returns)
* @deprecated because it does the same as html()
*/
public function html_with_no_data(){
$this->compileAsNeeded();
if ($this->html_with_no_data !== false)return $this->html_with_no_data;
// $phad_mode = 'display_with_empty_object';
$phad_block = \Phad\Blocks::VIEW;
$args = $this->args;
extract($args);
ob_start();
require($this->get_compiled_file_path());
$output = ob_get_clean();
$this->html_with_no_data = $output;
return $output;
}
///////////////////////////
//
/////// Utility & Compilation ////////
//
///////////////////////////
/**
* Get the item template's source code
*/
protected function source(){
return $this->source
?? ($this->source=file_get_contents($this->templateFile))
;
}
/**
* get path to compiled file
*/
public function get_compiled_file_path(){
$outFile = $this->path.'.compiled.php';
return $outFile;
}
/**
* Save content to the compiled file
*/
public function putCompiledFile($content){
$outFile = $this->get_compiled_file_path();
file_put_contents($outFile, $content);
}
/**
* Compile the item.
* @see compileAsNeeded() to conditionally compile only if the template has changed
*/
public function compile(){
$compiler = new \Phad\TemplateCompiler();
try {
$source = $this->source();
if (strlen(trim($source))==null)throw new \Exception("Cannot compile '".$this->templateFile."' because it is empty");
$src = $compiler->compile($source, file_get_contents(__DIR__.'/template/main.php'));
} catch (\Error $e){
$msg = 'Argument 1 passed to Taeluf\PHTML::insertCodeBefore() must be an instance of DOMNode, null given';
if (strpos($e->getMessage(),$msg)!==false){
throw new \Exception("\nThere was an error processing phad item '{$this->name}'. Maybe it has no html nodes? Idk.\n");
}
throw $e;
}
// $output = $compiler->output();
// $src = $output['view'];
$this->putCompiledFile($src);
}
/**
* Compile only if the template has changed or there is no compiled item output. Will only compile once per instance of this class, regardless of changes on disk.
*
* @return true if compilation was performed. false otherwise.
*/
public function compileAsNeeded(){
if ($this->instanceHasBeenCompiled){
return false;
} else if (!file_exists($this->get_compiled_file_path())){
$this->compile();
} else if ($this->force_compile){
$this->compile();
} else if (filemtime($this->templateFile)>=filemtime($this->get_compiled_file_path())){
$this->compile();
} else return false;
$this->instanceHasBeenCompiled = true;
return true;
}
/**
* Generate an sql CREATE TABLE statement from this item if it is a form
*
* @return string sql CREATE TABLE statement
*/
public function create_table_statement(): string {
$info = $this->info();
if (!is_object($info)||!isset($info->properties)){
$name = $this->name;
throw new \Exception("Cannot load form info for '$name'. Either: It is not a phad item, You cannot access it, or It does not have properties.");
}
$properties = $info->properties;
// print_r($properties);
$table = strtolower($info->name);
$statement = "CREATE TABLE `$table` (\n ";
$did_first = false;
foreach ($properties as $name=>$details){
$col_str = $this->col_str($name, $details);
if ($did_first)$statement .=",\n ";
$statement .= $col_str;
$did_first = true;
}
$statement .= "\n);";
return $statement;
}
/**
* Generate sql for create an individual column from node properties array
*
* @param $column_name the name of the column to make
* @param $dom_input the array of attributes and info about the html input
* @return a string like `name` VARCHAR(256)
*/
function col_str(string $column_name, array $dom_input){
$col = "`$column_name`";
if ($dom_input['tagName']=='textarea')return "$col TEXT";
if ($dom_input['tagName']=='select'){
$type = "ENUM('"
.implode("','", $dom_input['options'])
."')";
return "$col $type";
}
if ($dom_input['tagName']!='input')throw new \Exception("Cannot handle tagName '".$dom_input['tagName']."'");
$type = $dom_input['type'];
if ($type=='checkbox')return "$col TINYINT";
else if ($type=='hidden' && $column_name=='id')return "$col int unsigned PRIMARY KEY AUTO_INCREMENT";
else if ($type=='text'){
$len = 0;
if (isset($dom_input['maxlength']))$len = (int)$dom_input['maxlength'];
if ($len < 256)$len = 256;
return "$col VARCHAR($len)";
} else if ($type=='email'){
return "$col VARCHAR(256)";
} else if ($type=='phone'){
return "$col VARCHAR(20)";
} else if ($type=='radio'){
return "$col VARCHAR(256)";
} else if ($type=='number'){
return "$col int";
} else if ($type=='backend' && ($column_name=='id' || substr($column_name,-3)=='_id')){
return "$col int unsigned";
} else if ($type=='backend' && $column_name=='uuid'){
return "$col binary(16) NOT NULL DEFAULT (UUID_TO_BIN( UUID() ) )";
} else if ($type=='backend'){
return "$col VARCHAR(256)";
}
throw new \Exception("Cannot handle input type '$type'");
}
/**
* Output html. @see(html())
*/
public function __toString(){
return $this->html();
}
}